# file:: game_mechanics.rb
# author::  Ralph M. Churchill
# version::
# date::
#
# This source code copyright (C) 2006 by Ralph M. Churchill
# All rights reserved.
#
# Released under the terms of the GNU General Public License
# See LICENSE file for additional information.

require 'engine/action'

class UserException < RuntimeError
end

# -------------------------------------------------------------------------
# Mechanics of Game
# -------------------------------------------------------------------------
module GameMechanics
    # Parse user input and do one of the following:
    # 1. if the input is /  then repeat the last command
    # 1. if the input is '  then treat as "say"
    # 1. if the input is me then treat as "emote"
    # Then strip off the leading slash, split the args and remember the command
    # [+performer+]  Character performing the action
    # [+cmd_str+]   The user input
    def handle_command(performer,cmd_str)
        # do some basic shortcut handling
        case cmd_str
        when /^\/(\s)*$/
            cmd_str = performer.last_command 
        when /^'/
            cmd_str.insert(0,'say ').sub!(/'/,'')
        when /^me/
            cmd_str.sub!(/me/,'emote')
        end

        performer.last_command = cmd_str

        cmd_str = cmd_str.split
        cmd = cmd_str[0]
        args = cmd_str[1..-1]
        if command = performer.find_command(cmd.sub(/^\//,'')) then
            begin
                command.execute(args)
            rescue UsageException
                performer.do_action(Action.new(:error,performer,
                                   {:msg => "Usage: #{command.usage}"}))
            rescue => e
                $stderr.puts("#{command.name} failed to execute: #{e}")
            end
        else
            performer.do_action(Action.new(:error,performer,
                               {:msg => "Unrecognized command: #{cmd}"}))
        end
    end
    # Manage a player's "login". Add the character to the region and room and
    # send "enter" messages.
    # [+char+]  Character performing the action
    # == Messages
    # [enterregion]
    # [enterroom]
    def login(char)
        region,room = char.region,char.room
        region.add_character(char)
        room.add_character(char)
        char.logged_in = true

        enterregion = Action.new(:enterregion,char)
        region.do_action(enterregion)
        char.do_action(enterregion)

        enterroom = Action.new(:enterroom,char,{:room => char.room})
        ([room]+room.characters+room.items+char.items).each do |r|
            r.do_action(enterroom) if r and r.respond_to?(:do_action)
        end
    end
    # Manage's a player's "logout". Send "leave" messages, kill all
    # associated timers.
    # [+char+]  Character performing the action
    # == Messages
    # [leaveregion]
    # [leaveroom]
    def logout(char)
        region = char.region
        room = char.room

        leaveroom = Action.new(:leaveroom,char,{:room => char.room})
        ([room]+room.characters+room.items+char.items).each do |r|
            r.do_action(leaveroom) if r and r.respond_to?(:do_action)
        end
        leaveregion = Action.new(:leaveregion,char)
        region.do_action(leaveregion)
        char.do_action(leaveregion)

        room.del_character(char)
        region.del_character(char)
        char.kill_all_timers if char.respond_to?(:kill_all_timers)
        char.logged_in = false
    end
    # The game mechanic for speech
    # [+char+]  Character performing the action
    # [+msg+]   What the character is attempting to say
    # == Messages
    # [cansay]  Query
    # [say]     The actual speech
    def say(char,msg)
        room, region = char.room, char.region
        cansay = Action.new(:cansay,char,{:msg => msg})
        unless [char,room,region].find {|e| not e.do_action(cansay)}
            says = Action.new(:say,char,{:msg => msg})
            ([room,region]+room.characters).each {|e| e.do_action(says)}
        end
    end
    # The game mechanic for looking at an object.
    # [+char+]  Character performing the action
    # [+at+]   What the character is attempting to look at
    # == Messages
    # [canlook] Query
    # [see]     What the player sees
    # [announce] If the player looks at another player, inform them
    # [error]   Error
    def look(char,at)
        room,region = char.room,char.region
        canlook = Action.new(:canlook,char,{:target => at})
        unless [char,room,region].find {|e| not e.do_action(canlook)}
            if at.nil? or at.empty? then
                what = room
            else
                what = (room.characters+room.items+room.portals).find do |e|
                    e.named?(at)
                end
            end
            unless what.nil? then
                char.do_action(Action.new(:see,char,{:target => what}))
                if what.kind_of?(Character) and what != char
                    what.do_action(Action.new(:announce,what,
                                  {:msg => "#{char.name} look at you."}))
                end
            else
                char.do_action(Action.new(:error,char,
                              {:msg => "You don't see any #{at} here."}))
            end
        end
    end
    def use_item(char,item)
    end
    # not sure a separate "get_item" is needed...
    # The game mechanic for getting an item from someone or something, with an# {{{
    # option quantity.
    # [+char+]  Character performing the action
    # [+item+]  The Item the character is attempting to get
    # [+quantity+] The quantity of Item the character is getting, usually
    # defaulted to 1 by the Get command.
    # == Messages
    # [cangetitem]  Query
    # [getitem]     Take the item from someone/thing and give it to the player
    def get_item(opts={})
        [:who,:item].each{|r| raise "#{r} required!" unless opts.key?(r)}
        char = opts[:who]
        item = opts[:item]
        quantity = opts.key?(:quantity) ? opts[:quantity] : 1
    end# }}}
    def delete_item(item)
    end
    # Give an item to a room or a character, as specified
    # by options hash. Valid options:
    # [+from+] who/what is giving the item
    # [+to+]  who/what is receiving the item - Required
    # [+item+] the item being given - Required
    # [+quantity+] how many of the item. Default 1.
    # == Messages
    # [candropitem] Query if item can be given away
    # [canreceiveitem] Query if receiver can receive
    # [giveitem] Transfer the item from one entity to another
    def give_item(opts={})
        [:to,:item].each{|r| raise "#{r} required!" unless opts.key?(r)}
        from = opts[:from]
        to = opts[:to]
        item =opts[:item]
        quantity = opts.key?(:quantity) ? opts[:quantity] : 1

        # if the item belongs to a character, only that
        # character can give it
        return unless item.character.nil? or item.character == from
        # items cannot be "given" across different rooms
        unless from.nil? 
            return unless (from.respond_to?(:room) ? from.room : from) == \
                (to.respond_to?(:room) ? to.room : to)
        end

        quantity = item.quantity if quantity > item.quantity

        unless from.nil?
            candrop = Action.new(:candropitem,from,
                                 {:item => item, :quantity => quantity})
            return unless (item.do_action(candrop) and
                           from.do_action(candrop))
        end
        canreceive = Action.new(:canreceiveitem,to,
                                {:from => from,:item => item,:quantity => quantity})
        return unless (item.do_action(canreceive) and to.do_action(canreceive))

        if (item.many? and quantity != item.quantity) and
            item.quantity - quantity > 0 then
            # create remainder
        end
        
        case to
        when Character
            item.instance_variable_get("@props")[:region_id] = nil
            item.instance_variable_get("@props")[:room_id] = nil
            item.character = to
        when Room
            item.region = to.region
            item.room = to
            item.instance_variable_get("@props")[:character_id] = nil
        end

        unless from.nil?
            from.del_item(item)
            # hrm. what is this for?
=begin
            ([from.room]+from.room.characters+from.room.items).each do |e|
                e.do_action(Action.new(:giveitem,from,
                                       {:to => to,
                                       :item => item,
                                       :quantity => quantity}))
            end
=end
        end
        # join_quantities
    end
    # This is the general game mechanic for movement. An entity is transported
    # from one place to another as specified by options hash.
    #
    # Locations are defined in a hierarchy where a Room
    # exists in a Region. Rooms are linked by a Portal
    # which can, in turn, link Regions.
    # 
    # If movement results in the changing of the character's Region, perform the
    # appropriate queries and actions, otherwise ignore.
    #
    # Valid options:
    # [+char+] The Character performing the action
    # [+newroom+] The character's destination, we already know the start
    # (char.room)
    # [+portal+] Movement is *generally* through a portal, except in cases of
    # teleportation, GMing, etc.
    # == Messages
    # [canleaveregion] Query
    # [canenterregion] Query
    # [canleaveroom] Query
    # [canenterroom] Query
    # [canenterportal] Query
    # [leaveregion] Leave the region
    # [leaveroom] Leave the room
    # [enterportal] Enter the portal
    # [enterregion] Arrive in the new region
    # [enterroom] Arrive in the new room
    def transport(opts={})
        [:who,:where].each{|r| raise "#{r} required!" unless opts.key?(r)}
        char = opts[:who]
        newroom = opts[:where]
        portal = opts[:portal]
        oldroom = char.room
        oldregion = char.region
        newregion = newroom.region

        # ---------------------------------------------------------------------
        # Query
        # ---------------------------------------------------------------------
        if (not oldregion.nil?) and oldregion != newregion
            canleaveregion = Action.new(:canleaveregion,char,
                                        {:region => oldregion})
            canenterregion = Action.new(:canenterregion,char,
                                        {:region => newregion})
            return unless (oldregion.do_action(canleaveregion) and
                           newregion.do_action(canenterregion) and
                           char.do_action(canleaveregion) and
                           char.do_action(canenterregion))
        end

        unless oldroom.nil?
            canleaveroom = Action.new(:canleaveroom,char,{:room => oldroom})
            return unless (oldroom.do_action(canleaveroom) and
                           char.do_action(canleaveroom))
        end
        canenterroom = Action.new(:canenterroom,char,{:room => newroom})
        return unless (newroom.do_action(canenterroom) and
                       char.do_action(canenterroom))
        return unless portal.do_action(Action.new(
                                      :canenterportal,char)) if portal

        # ---------------------------------------------------------------------
        # Move
        # ---------------------------------------------------------------------
        char.region = newregion if oldregion != newregion
        char.room = newroom

        # ---------------------------------------------------------------------
        # Notify
        # ---------------------------------------------------------------------
        [oldregion,char].each do |e|
           e.do_action(Action.new(:leaveregion,char,{:region => oldregion}))
        end if (not oldregion.nil?) and oldregion != newregion

        ([oldroom]+oldroom.characters+oldroom.items+[char]+char.items).each do |e|
            e.do_action(Action.new(:leaveroom,char,
                                   {:room => oldroom, :portal => portal}))
        end unless oldroom.nil?

        [portal,char].each do |e|
            e.do_action(Action.new(:enterportal,char,{:portal => portal}))
        end unless portal.nil?

        [newregion,char].each do |e|
            e.do_action(Action.new(:enterregion,char,{:region => newregion}))
        end if oldregion != newregion

        ([newroom]+newroom.characters+newroom.items+char.items).each do |e|
            e.do_action(Action.new(:enterroom,char,
                                   {:room => newroom,:portal => portal}))
        end
    end
    # Creates an Item from an ItemTemplate in a Room or on a Character (i.e. in
    # his or her inventory).
    # [+template_id+] Item Template ID
    # [+where+] Room or Character in/on which the item should spawn
    def spawn_item(template_id,where)
        template = ItemDatabase.instance.get_template(template_id)
        newitem = ItemDatabase.instance.generate_from_template(template)

        give_item({:item => newitem,:to => where})
    end
    # Creates a Character from a CharacterTemplate in a Room
    # [+template_id+] Character Template ID
    # [+where+] Room in which the Character should spawn
    def spawn_character(template_id,where)
        template = CharacterDatabase.instance.get_template(template_id)
        newchar = CharacterDatabase.instance.generate_from_template(template)

        transport({:who => newchar, :where => where})
=begin
        [where,where.region].each do |w|# {{{
            w.do_action(Action.new(:spawnchar,newchar))
        end# }}}
=end
    end
end
